package net.luohuasheng.bee.proxy.core.utils.classloader;


import net.luohuasheng.bee.proxy.core.utils.CastUtils;
import net.luohuasheng.bee.proxy.core.utils.ClassUtils;
import net.luohuasheng.bee.proxy.core.utils.ColumnUtils;
import net.luohuasheng.bee.proxy.core.utils.ExpireMap;
import net.luohuasheng.bee.proxy.core.utils.classloader.dto.ClassMethodDto;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * 类操作工具
 *
 * @author luohuasheng
 * @date 2020/4/4 17:19
 */
public class ClassLoaderUtils {

    private static final ExpireMap<Class<?>, Map<String, ClassMethodDto<?>>> CLASS_CACHE = new ExpireMap<>(3600 * 1000, 1000);

    private final static String BOOL_START = "is";


    public static ClassMethodDto<?> getClassMethod(Class<?> classZ, String name) {
        Map<String, ClassMethodDto<?>> classMethodDtoMap = getClassMethod(classZ);
        return classMethodDtoMap.getOrDefault(name.toUpperCase(), classMethodDtoMap.get(ColumnUtils.toCamelCase(name)));
    }

    private static PropertyDescriptor getPropertyDescriptor(String fieldName, Class<?> classZ) {

        try {
            return new PropertyDescriptor(fieldName, classZ);
        } catch (IntrospectionException e) {
            if (fieldName.startsWith(BOOL_START)) {
                try {
                    return new PropertyDescriptor(fieldName.substring(2), classZ);
                } catch (IntrospectionException ex) {
                    return null;
                }
            } else {
                return null;
            }
        }
    }

    public static <V> Map<String, ClassMethodDto<V>> getClassMethod(Class<?> classZ, ColumnLoader<V> loader) {
        return CastUtils.cast(CLASS_CACHE.computeIfAbsent(classZ, v -> loadClassMethod(classZ, loader)));
    }

    public static Map<String, ClassMethodDto<?>> getClassMethod(Class<?> classZ) {
        return CLASS_CACHE.computeIfAbsent(classZ, v -> loadClassMethod(classZ, null));
    }

    private static <V> Map<String, ClassMethodDto<?>> loadClassMethod(Class<?> classZ, ColumnLoader<V> loader) {
        Map<String, ClassMethodDto<?>> map = new LinkedHashMap<>(0);
        List<Field> fields = ClassUtils.getAllFieldsExcludeTransient(classZ);
        for (Field field : fields) {
            ClassMethodDto<V> classMethodDto = new ClassMethodDto<>();
            PropertyDescriptor descriptor = getPropertyDescriptor(field.getName(), classZ);
            if (descriptor == null) {
                continue;
            }
            Method readMethod = descriptor.getReadMethod();
            Method writeMethod = descriptor.getWriteMethod();
            if (readMethod != null && writeMethod != null) {
                classMethodDto.setProperty(field.getName());
                if (loader != null) {
                    loader.load(classMethodDto, readMethod, writeMethod, field);
                }
                classMethodDto.setReadMethod(readMethod);
                classMethodDto.setField(field);
                classMethodDto.setWriteMethod(writeMethod);
                map.put(classMethodDto.getProperty(), classMethodDto);
            }
        }
        return map;
    }
}
